package org.softmed.rest.server.defaults.generation;

import groovy.lang.GroovyClassLoader;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;
import org.softmed.filehandling.ClassFinder;
import org.softmed.filehandling.FileUtil;
import org.softmed.filehandling.selectors.GroovyFileSelector;
import org.softmed.filehandling.selectors.JavaFileSelector;
import org.softmed.reflection.utils.ReflectionUtil;
import org.softmed.rest.generation.scafold.AppConfig;
import org.softmed.rest.generation.scafold.ModConfig;
import org.softmed.rest.generation.scafold.build.ApplicationProcessor;

import com.google.inject.Inject;

public class DefaultClassReader implements ApplicationProcessor {

	@Inject
	GroovyClassLoader groovyClassLoader;

	FileUtil util = new FileUtil();
	ReflectionUtil reflect = new ReflectionUtil();
	ClassFinder classFinder = new ClassFinder(new JavaFileSelector(),
			new GroovyFileSelector());

	@Override
	public void process(List<AppConfig> apps) throws Throwable {
		
//		System.out.println("GROOVY CLASS LOADER: " + groovyClassLoader);
		
		
		groovyClassLoader.clearCache();
		groovyClassLoader.setShouldRecompile(true);
		// 

		for (AppConfig app : apps) {
			getClasses(app);
			listClasses(app);
		}

		System.out.println("Classes Read");

	}

	private void listClasses(AppConfig app) {
		System.out.println("\nListing found classes:");
		List<ModConfig> mods = app.getModules();
		for (ModConfig modConfig : mods) {

			List<Class> classes = modConfig.getClasses();
			if (classes.size() == 0)
				System.out.println("\n\nApp " + app.getName() + " - Mod "
						+ modConfig.getName() + " : no classes found.");
			else {
				System.out.println("\n\nApp " + app.getName() + " - Mod "
						+ modConfig.getName() + " : found " + classes.size()
						+ " classes:\n");
				for (Class type : classes) {
					System.out.println(type.getCanonicalName());
				}
			}
		}
		System.out.println("\nFinished listing found classes");

	}

	private void getClasses(AppConfig app) throws Throwable {
		List<ModConfig> mods = app.getModules();
		for (ModConfig mod : mods) {
			try {
				getClasses(app, mod);
			} catch (Throwable t) {
				t.printStackTrace();
				System.err.println("Error getting classes for module "
						+ mod.getName() + " of application " + app.getClass());
			}
		}

	}

	private void getClasses(AppConfig app, ModConfig mod) throws Throwable {

		File root = new File(mod.getPath());
		File[] files = root.listFiles();

		if (files != null)
			for (File file : files) {
				if (file.isDirectory() && file.getName().equals("entities"))
					getClasses(file, app, mod);
			}

	}

	private void getClasses(File file, AppConfig app, ModConfig mod)
			throws Throwable {
		File[] files = file.listFiles();
		for (File child : files) {
			String name = child.getName();
			if (name.endsWith(".groovy") || name.endsWith(".gc"))
				addGroovyClass(child, app, mod);
			else if (name.equals("classes.txt"))
				addReferencedClasses(child, app, mod);
		}

	}

	private void addGroovyClass(File child, AppConfig app, ModConfig mod)
			throws Throwable {
		try {

			// URL[] uris = groovyClassLoader.getURLs();
			// System.out.println("Listing URIS:");
			// for (URL url : uris) {
			// System.out.println(url);
			// }
			// System.out.println("DONE Listing URIS:");

			// System.out.println("Reading Groovy source from file :" + child);
			String groovyClassCode = util.readFromFile(child);
			GroovyClassLoader temp = new GroovyClassLoader(groovyClassLoader);
			// addClassPath(temp, mod);
			Class test = temp.parseClass(groovyClassCode);
			// groovyClassLoader.
			try {
				// System.err.println("Trying to check if class "
				// + test.getSimpleName()
				// + " is already loaded in groovy class loader");
				Class exists = groovyClassLoader.loadClass(test
						.getCanonicalName());
				addClass(app, mod, exists);
				// System.err.println("yeap it's loaded");
				// System.out.println("Class " + test.getCanonicalName()
				// + " already loaded in the class loader");
			} catch (Throwable t) {
				// t.printStackTrace();
				// System.err
				// .println("It wasn't found so now I'm going to define it!");
				// addClassPath(groovyClassLoader, mod);
				Class type = groovyClassLoader.parseClass(child);
				addClass(app, mod, type);
				// System.err.println("and cue defined!!!");
			}

		} catch (Throwable t) {
			System.err.println("Error compiling groovy class "
					+ child.getName() + " for module " + mod.getName()
					+ " of application " + app.getName());
			System.err.println("Full Path of Groovy Class : "
					+ child.getCanonicalPath());
			t.printStackTrace();
			return;
		}
		// Class type = groovyClassLoader.parseClass(child);
		System.out.println("wazza");
	}

	private void addClassPath(GroovyClassLoader classLoader, ModConfig mod) {
		String path = '/' + mod.getPath().replace('\\', '/') + "/" + "entities";
		classLoader.addClasspath(path);
		System.out.println("Added classpath: " + path);
		URL[] uris = classLoader.getURLs();
		System.out.println("Listing URIS:");
		for (URL url : uris) {
			System.out.println(url);
		}
		System.out.println("DONE Listing URIS:");
	}

	private void addClass(AppConfig app, ModConfig mod, Class type)
			throws IOException {
		if (type == null) {
			System.err.println("Trying to add a null class for Module "
					+ mod.getName() + " in App " + app.getName());
			return;
		}

		if (app.getClasses().contains(type))
			throw new RuntimeException("Class " + type
					+ " defined in application " + app.getName()
					+ " for module " + mod.getName() + " is already registred");

		// System.out.println("Class " + type
		// + " is defined in application " + app.getName()
		// + " for module " + mod.getName() + "");

		app.getClasses().add(type);
		mod.getClasses().add(type);
	}

	private void addReferencedClasses(File child, AppConfig app, ModConfig mod)
			throws Throwable {
		String contents = util.readFromFile(child);
		StringTokenizer st = new StringTokenizer(contents);
		String reference = null;
		while (st.hasMoreTokens()) {
			reference = st.nextToken("\n");
			if (reference == null || reference.length() == 0)
				continue;

			if (reference.endsWith(".*"))
				addPackageClasses(reference, app, mod);
			else
				addReferencedClass(reference, app, mod);
		}

	}

	private void addPackageClasses(String reference, AppConfig app,
			ModConfig mod) throws Throwable {
		String corrected = reference.substring(0, reference.lastIndexOf('.'));
		Collection<Class> classes = classFinder.getClasses(corrected);
		for (Class type : classes) {
			addClass(app, mod, type);
		}

	}

	private void addReferencedClass(String reference, AppConfig app,
			ModConfig mod) throws Throwable {
		Class<?> type = null;
		try {
			type = Class.forName(reference);
			// type = groovyClassLoader.loadClass(reference);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
			String groovy = reference.replace('.', '/') + ".groovy";
			File file = util.getFile(groovy);
			if (file.exists()) {
				addGroovyClass(file, app, mod);
				return;
			}

			groovy = reference.replace('.', '/') + ".gc";
			file = util.getFile(groovy);
			if (file.exists()) {
				addGroovyClass(file, app, mod);
				return;
			}

			throw new RuntimeException("Couldn't find reference class : "
					+ reference + " in classes.text file of app "
					+ app.getName() + " in mod " + mod.getName());
		}

		addClass(app, mod, type);

	}
}
